feat(control): show command limits and measured response tiers - #1474
Conversation
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Control proof restarted on every command that differed by 100 W, so modes that retune the battery each tick showed "waiting" most of the time and never warned when the battery stopped following. A cloud charger never reached measured proof, a kettle after a confirmed step removed the confirmation, and a battery tapering near full raised a warning. Retuning now continues one measurement record. Each reading is compared with the commands that could still be in force during the device's response time; only a material step (500 W or the tolerance) starts a new comparison. A source's declared power_max_age_s sets its freshness everywhere. The site comparison freezes once its post-step window is complete. A charge shortfall at 90% SoC or more, or a discharge shortfall at 10% or less, is information. Core now states the answer: status (following, waiting, limited, not_following, no_contact, not_controlled), severity (info, warning, alarm), evidence (accepted, measured, confirmed), confirmed_at_ms and readings_fresh. Lost measured proof becomes readings_lost. Site numbers live in site_evidence and are omitted when not computed. Trace tests replay self-consumption, an ignored battery, a kettle, a taper and a cloud charger. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
A tap on a bubble opened the manual hold form with "Charge 3000 W" preselected, and the answer sat folded below it in tier labels. The sheet now starts with "Are we in control?": one status, one sentence with the measured numbers and, when the owner can act, the next step. "How FTW knows" lists sent, accepted, measured and confirmed evidence, with numbers and the response curve folded inside. Manual override folds below; Stop sits in the active-hold banner. The overview stays quiet while FTW is in control. Only Core's warning and alarm draw a mark, an amber triangle or a red disc, and a combined bubble keeps the worst. The Values view lists each controlled device with its answer. Words come from Core's status, severity and reason; a test fails when Core can emit a reason without words. The EV status line keeps power first. The shared module holds no DOM code; the box renderer lives in control-feedback-view.js. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Replace the tier ladder and bubble layout rules in VISION.md with the status the owner sees and the receipt behind it. Keep the paragraph about battery learning, but after the roadmap table so the Later row stays in it. The changeset now describes the shipped behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
The app never shows a bare minus sign. Device, grid and unexplained changes now read "toward discharge", "toward export" or "more drawn". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Paired with srcfl/ftw#1474 at 6716098a. The box now states status, severity and evidence; the app renders them instead of deriving its own tone from reasons, so a stale grid meter no longer paints every bubble red and a finished car reads "Car is full" instead of an unknown state. The panels open with the answer, one sentence and the next step, and keep "How FTW knows" folded with an evidence receipt, numbers and curves. Marks appear only for warning (amber) and alarm (red). The charging status line keeps power first; a warning no longer replaces it. The control words are now vendored from the box under src/vendor/ftw with a provenance header and recorded digest, like the flow component, so drift fails the vendored test. The box's DOM renderer no longer ships here: the entry bundle is 82.6 kB gzip, down from 85.1 kB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Easee's cloud records power only when it changes; a steady charge kept an old source time, so measured proof lapsed and the lost-control alarm fired during normal charging. A driver may now set control_power_confirmed when the source still hears from the device and the value is unchanged. Core treats that reading as a measurement at the time it arrived: it keeps a steady charge measured, and a charger that holds its old power after a new target now reads as not following instead of waiting forever. Sources that do not confirm keep the old rule: a repeated sample adds nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Paired with srcfl/ftw#1474, which now reads control_power_confirmed. Easee's cloud records power only on change, so a steady charge kept an old source time and Core raised a lost-control alarm during normal charging. The driver now marks the unchanged value confirmed while the cloud still hears from the charger; the source time stays for distinct samples. Pixii's AC power (40083) is SunSpec generator frame, like its setpoint, so control_power_w is now negated into site signs. A following battery read as the wrong direction before. Hardware has not verified either sign yet. Sungrow claims external_meter only when the meter reads power or phase current; a meterless install reads zero and claims none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Moves the bundled snapshot to srcfl/device-drivers#149 at d74ace6a: Easee marks a steady value confirmed while the cloud hears from the charger, Pixii's control power leaves SunSpec's generator frame, and Sungrow claims a separate meter only when one reads power or phase current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Keep SiteMeasurementSources beside master's haOwner.Bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 857fc9aa72
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if len(c.Recent) > 8 { | ||
| c.Recent = c.Recent[len(c.Recent)-8:] |
There was a problem hiding this comment.
Retain commands for the full response window
With the default two-second control loop, limiting Recent to eight commands preserves only about 14 seconds of targets, while ResponseDelay("ev") explicitly allows a charger two minutes to respond. For a surplus-controlled charger whose target is retuned by small amounts each tick, a fresh reading can therefore match a command that is still legitimately in force but has already been discarded; commandGap then records a persistent mismatch and the dashboard eventually reports a correctly following charger as not_following. Retain history by response-window age, rather than a fixed entry count.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
| if kind == DerPV { | ||
| readbackMismatch = math.Abs(math.Abs(*d.SetpointW)-math.Abs(*c.PowerW)) > math.Max(100, math.Abs(*c.PowerW)*0.05) |
There was a problem hiding this comment.
Compare PV readback against delayed commands
When a PV ceiling is continuously retuned and the inverter reports the applied setpoint with normal delay, this PV-only override ignores the response-aware commandGap calculated immediately above and compares the readback solely with the newest command. Even if the inverter follows every command within the declared 15-second response window, the mismatch timer can remain set for 30 seconds and surface a false setpoint_changed warning. Apply the same in-force-command window while preserving PV's absolute-sign comparison.
AGENTS.md reference: AGENTS.md:L22-L23
Useful? React with 👍 / 👎.
* fix(drivers): expose current limits and measured control response Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * fix(easee): retain power source time for control evidence Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Report Sungrow setpoint and external meter evidence Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * fix(drivers): keep steady Easee power current and fix control signs Paired with srcfl/ftw#1474, which now reads control_power_confirmed. Easee's cloud records power only on change, so a steady charge kept an old source time and Core raised a lost-control alarm during normal charging. The driver now marks the unchanged value confirmed while the cloud still hears from the charger; the source time stays for distinct samples. Pixii's AC power (40083) is SunSpec generator frame, like its setpoint, so control_power_w is now negated into site signs. A following battery read as the wrong direction before. Hardware has not verified either sign yet. Sungrow claims external_meter only when the meter reads power or phase current; a meterless install reads zero and claims none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> --------- Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…75) * feat(control): show limits and measured response in Now and charging Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * feat(control): show measured response curves and unexplained changes Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * fix(control): distinguish measurement sources from active control Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Show measured timing in milliseconds and name missing sources Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Show per-device proof status and verification-loss alarms Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Keep per-device control proof in overview bubbles and a detail dialog Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Use small quiet control indicators and keep full evidence on tap Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Open device panels directly and fold evidence below controls Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Show confirmed partial power separately from target shortfall Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Explain the full-battery charging pause Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> * Answer "Are we in control?" first, with the box's words Paired with srcfl/ftw#1474 at 6716098a. The box now states status, severity and evidence; the app renders them instead of deriving its own tone from reasons, so a stale grid meter no longer paints every bubble red and a finished car reads "Car is full" instead of an unknown state. The panels open with the answer, one sentence and the next step, and keep "How FTW knows" folded with an evidence receipt, numbers and curves. Marks appear only for warning (amber) and alarm (red). The charging status line keeps power first; a warning no longer replaces it. The control words are now vendored from the box under src/vendor/ftw with a provenance header and recorded digest, like the flow component, so drift fails the vendored test. The box's DOM renderer no longer ships here: the entry bundle is 82.6 kB gzip, down from 85.1 kB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> --------- Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#1474 pinned d74ace6, a commit on the #149 branch. #149 has now landed on device-drivers main as 424f1e5c. Moving the pin there keeps the bundle on main and brings esphome_dsmr 1.0.7 (srcfl/device-drivers#152), the only bundled driver that differs. Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Problem and result
FTW sent commands without showing whether a device did what it asked. Core now answers "Are we in control?" for each controlled device function (battery, charger, solar cap, V2X) in every mode, and a tap on a device shows that answer first.
Core decides the status and how urgent it is; clients only choose words and colours:
The evidence is a receipt behind the answer, not the headline: sent, accepted, measured and confirmed (a separate grid meter saw the matching change). The API carries
status,severity(info, warning, alarm),evidence(none, accepted, measured, confirmed),reason,readings_fresh,confirmed_at_msandsite_evidence. Support reports include the same rows.What the 1 October review changed
The review replayed site traces through telemetry, command evidence and the API on a fake clock. The first version restarted proof whenever a command moved by 100 W and assumed one steady command at a time. These gaps are fixed and each has a trace test:
How: retuning continues one measurement record and compares each reading with the commands that could still be in force during the response time; only a material step (500 W or the tolerance) starts a new comparison. A source's
power_max_age_ssets its freshness everywhere. A change-only source may setcontrol_power_confirmed. The site comparison freezes once its post-step window is complete. A charge shortfall at 90% SoC or more, or a discharge shortfall at 10% or less, is information.The device sheet now opens with the answer, one sentence and the next step; "How FTW knows" folds the receipt, numbers and curve, and manual override folds below. The overview draws a mark only for warnings and alarms. Tier numbers are gone from the interface. Six reason texts that Core never sent are removed, and a test fails when Core can send a reason without words. The VISION.md section is rewritten at principle level, and the roadmap table is whole again.
Scope and limits
Validation
make verifypassed at 6716098 (vet, all Go packages, build).api,telemetryanddriverspassed again at b490ea2 with the new driver pin.Paired with srcfl/ftw-webapp#75 (9729a9f) and srcfl/device-drivers#149 (d74ace6, pinned here). Registry and design tokens are unchanged.
Checklist
🤖 Generated with Claude Code